﻿// <copyright file="AbstractModMatcher.cs" company="SingularityShift">
// Copyright (c) 2009 SingularityShift.com
// </copyright>
// <author>Ben McClure</author>
// <email>ben@singularityshift.com</email>
// <date>2009-08-14</date>
// <summary>Implements most of the IModMatcher functionality for other classes to use</summary>
namespace GameLib.Games
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Xml.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Windows.Forms;
    using GameLib.Games.Components;

    /// <summary>
    /// <para>An implementation of an object that can match against a mod or a list of mods.</para>
    /// <para>Each instance of an implementing class should match against a mod or mod file pattern.</para>
    /// </summary>
    public abstract class AbstractModMatcher : IModMatcher
    {
        /// <summary>
        /// <para>Gets or sets the IMod which owns (is the parent of) this matcher.</para>
        /// <para>Generally, a matcher is designed to never match its owner.</para>
        /// </summary>
        [XmlIgnore]
        public IMod Owner
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the path to the file or filePattern that matches.
        /// </summary>
        public string FilePath
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the file or file pattern (as defined by the OS) that this matcher should match against.
        /// </summary>
        public string FilePattern
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a message regarding this matcher which can be displayed to the user. What this message means should be defined by the implementing class.
        /// </summary>
        public string Message
        {
            get;
            set;
        }

        /// <summary>
        /// Gets a value indicating whether this matcher contains any useful information
        /// </summary>
        public virtual bool IsEmpty
        {
            get
            {
                if (!string.IsNullOrEmpty(FilePath))
                {
                    return false;
                }

                if (!string.IsNullOrEmpty(FilePattern))
                {
                    return false;
                }

                if (!string.IsNullOrEmpty(Message))
                {
                    return false;
                }

                return true;
            }
        }

        /// <summary>
        /// <para>Gets or sets the priority of this matcher as compared to (a) other matchers of the owner mod, and (b) other matchers that match the same thing.</para>
        /// <para>If two matchers match the same thing, the logic which determines which one is 'active' is loosely this:
        /// 1. If the priorities are the same, both will match.
        /// 2. If one has a priority and the other does not, the matcher with a priority will match.
        /// 3. If neither has a priority, both will match.
        /// 4. If one priority is determined Greater Than the other, it will match.</para>
        /// </summary>
        public int Priority
        {
            get;
            set;
        }

        /// <summary>
        /// Returns a list of IMods from the provided IModList which match this IModMatcher.
        /// </summary>
        /// <param name="modList">The IModList to match against.</param>
        /// <returns>A list of IMods which match this IModMatcher.</returns>
        public virtual IModCollection<T> Match<T>(IModCollection<T> modList) where T : IMod
        {
            return this.Match<T>(modList, this.Owner);
        }

        /// <summary>
        /// Returns a list of IMods from the provided IModList which match this IModMatcher.
        /// </summary>
        /// <param name="modList">The IModList to match against.</param>
        /// <param name="excludeMod">Excludes the privided mod from any matches</param>
        /// <returns>A list of IMods which match this IModMatcher.</returns>
        public virtual IModCollection<T> Match<T>(IModCollection<T> modList, IMod excludeMod) where T : IMod
        {
            IModCollection<T> excludeMods = new ModCollection<T>((IGame<T>)this.Owner.Owner);
            excludeMods.Add((T)excludeMod);
            return this.Match(modList, excludeMods);
        }

        /// <summary>
        /// Returns a list of IMods from the provided IModList which match this IModMatcher.
        /// </summary>
        /// <param name="modList">The IModList to match against.</param>
        /// <param name="excludeMods">An IModList of mods to exclude from the results.</param>
        /// <returns>A list of IMods which match this IModMatcher.</returns>
        public virtual IModCollection<T> Match<T>(IModCollection<T> modList, IModCollection<T> excludeMods) where T : IMod
        {
            IModCollection<T> result = new ModCollection<T>((IGame<T>)this.Owner.Owner);
            foreach (T mod in modList)
            {
                if ((!mod.Equals(Owner)) && (!excludeMods.Contains(mod)) && this.Matches(mod))
                {
                    result.Add(mod);
                }
            }

            return result;
        }

        /// <summary>
        /// Determines whether this matcher matches any mods in the provided ModList.
        /// </summary>
        /// <param name="modList">The IModList to match against.</param>
        /// <returns>A bool indicating whether this matcher matches any mods in the provided list.</returns>
        public virtual bool Matches<T>(IModCollection<T> modList) where T : IMod
        {
            return this.Matches(modList, new ModCollection<T>((IGame<T>)this.Owner.Owner));
        }

        /// <summary>
        /// Determines whether this matcher matches any mods in the provided ModList.
        /// </summary>
        /// <param name="modList">The IModList to match against.</param>
        /// <param name="excludeMod">an IMod to exclude from the result.</param>
        /// <returns>A bool indicating whether this matcher matches any mods in the provided list.</returns>
        public virtual bool Matches<T>(IModCollection<T> modList, IMod excludeMod) where T : IMod
        {
            IModCollection<T> excludeMods = new ModCollection<T>((IGame<T>)this.Owner.Owner);
            excludeMods.Add((T)excludeMod);
            return this.Matches(modList, excludeMods);
        }

        /// <summary>
        /// Determines whether this matcher matches any mods in the provided ModList.
        /// </summary>
        /// <param name="modList">The IModList to match against.</param>
        /// <param name="excludeMods">An IModList of IMods to exclude from the result.</param>
        /// <returns>A bool indicating whether this matcher matches any mods in the provided list.</returns>
        public virtual bool Matches<T>(IModCollection<T> modList, IModCollection<T> excludeMods) where T : IMod
        {
            foreach (T mod in modList)
            {
                if (mod.Equals(Owner))
                {
                    return false;
                }

                if (this.Matches(mod) && (!excludeMods.Contains(mod)))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines whether this matcher matches the provided IMod.
        /// </summary>
        /// <param name="theMod">The IMod to match against</param>
        /// <returns>A bool indicating whether this matcher matches the provided mod.</returns>
        public virtual bool Matches(IMod theMod)
        {
            if (theMod.Equals(Owner))
            {
                return false;
            }

            if (string.IsNullOrEmpty(FilePattern))
            {
                return false;
            }

            string filePattern = (this.FilePattern.Substring(0, 1).Equals("@")) ? this.FilePattern.Substring(1) : Regex.Escape(this.FilePattern);
            Regex regex = new Regex(filePattern, RegexOptions.IgnoreCase);
            return regex.IsMatch(theMod.Id);
        }

        /// <summary>
        /// Creates a deep copy of this object
        /// </summary>
        /// <returns>A deep copy of this object, equal to but not the same object as the one it was called against.</returns>
        public abstract object Clone();

        /// <summary>
        /// An override for determining if this Matcher equals another.
        /// </summary>
        /// <param name="obj">The object to check for equality against</param>
        /// <returns>A value indicating whether the objects are equal</returns>
        public override bool Equals(object obj)
        {
            if (!(obj is AbstractModMatcher))
            {
                return false;
            }

            return Equals((AbstractModMatcher)obj);
        }

        /// <summary>
        /// A custom method for determining if this Matcher equals another.
        /// </summary>
        /// <param name="obj">The Matcher to check for equality against</param>
        /// <returns>A value indicating whether the objects are equal</returns>
        public bool Equals(AbstractModMatcher obj)
        {
            if (!FilePath.Equals(obj.FilePath))
            {
                return false;
            }

            if (!FilePattern.Equals(obj.FilePattern))
            {
                return false;
            }

            if (!Message.Equals(obj.Message))
            {
                return false;
            }

            if (!Priority.Equals(obj.Priority))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Gets a unique has code
        /// </summary>
        /// <returns>A has code based on the FilePath, FilePattern, Message, and Priority</returns>
        public override int GetHashCode()
        {
            return FilePath.GetHashCode() ^ FilePattern.GetHashCode() ^ Message.GetHashCode() ^ Priority.GetHashCode();
        }
    }
}
